# -*- coding: utf-8 -*-

"""
自动采集相关。
"""

import os
import json
import hashlib
import datetime
import base64

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

from .log import get_logger
from .config import AGENT_PATH
from .cmdb import CMDBResource
from .common import single_command2


logger = get_logger(__name__, "INFO")


def get_service_node(pType=None, alive=True):
    """从Agent缓存获取服务节点。
    
    参数：
    - pType：服务节点类型；
    - alive：是否获取活动的服务节点，默认值为True；
    """
    fPath = os.path.join(AGENT_PATH, "easy_collector", "tmp", "service_nodes.json")

    try:
        with open(fPath, "r") as f:
            content = f.read()
            sns = json.loads(content)
    except Exception as e:
        logger.error(u"无法读取缓存得服务节点文件：%s" % e)
        return {}
        
    if pType is None and not alive:
        return sns
    else:
        ret = {}
        for snId, sn in sns.items():
            filterAlive = sn.get("status") == "alive" if alive else True
            filterPType = sn.get("provider", {}).get("ptype") == pType if pType else True
            if filterAlive and filterPType:
                ret[snId] = sn
        return ret


def get_md5(str):
    """获取字符串md5值。"""
    return hashlib.md5(str).hexdigest()


def get_alive_service(pType, localIp):
    """将指定类型的服务节点聚合为服务，聚合条件为具有相同的命令行和启动命令。
    
    参数：
    - pType：服务节点类型；
    - localIp：Agent所在的主机IP；
    """
    sns = get_service_node(pType)
    ss = {}

    for _, sn in sns.items():
        # 服务标识符
        cwd = sn["provider"]["cwd"].strip()
        cmdLine = sn["provider"]["cmdline"].strip()
        sUid = "-".join((pType, localIp, get_md5(cwd), get_md5(cmdLine)))
        if sUid not in ss:
            ss[sUid] = {
                "cwd": cwd,
                "cmd": cmdLine,
            }
    
    return ss


class AutoDiscovery(object):
    """为数据添加自动采集上报相关配置信息。"""
    global autoDiscoveryDataList
    autoDiscoveryDataList = []

    def data_format(self, jsonData, objectId, pks, upsert=False):
        """构造上报数据。

        参数:
        - jsonData：上报数据
        - objectId：模型ID
        - pks：唯一键列表
        - upsert：是否自动创建,布尔值
        """
        now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        for data in jsonData:
            data["autoCollectTime"] = now
            result = {
                "dims":{
                    "pks": pks,
                    "object_id": objectId,
                    "upsert": upsert
                },
                "vals": data
            }
            autoDiscoveryDataList.append(result)

    def report_data(self):
        """数据上报。"""
        print("-----BEGIN GATHERING DATA-----")
        print(json.dumps(autoDiscoveryDataList))
        print("-----END GATHERING DATA-----")


def base64_decode(cipher):
    """base64解码。"""
    clear = base64.b64decode(cipher)
    return clear


def decryption(cipher, keyPairModel="RSA_KEY_PAIR"):
    """解密，根据存储在CMDB的私钥解密。"""
    query = {
        "name": {
            "$eq": "activate",
        },
    }
    fields = {
        "name": True,
        "privateKey": True,
    }
    
    # cmdb工具
    keyPairModelObj = CMDBResource(keyPairModel)
    privateKey = keyPairModelObj.search_v3(query=query, fields=fields)

    if not privateKey:
        logger.error(u"未获取到私钥")
        return ""
    
    try:
        privateKey = privateKey[0]["privateKey"]
        privateKey = RSA.importKey(base64_decode(privateKey))
        decipher = PKCS1_OAEP.new(privateKey)
        clear = decipher.decrypt(base64_decode(cipher))
    except Exception as e:
        logger.error("解密失败：%s" % e)
    else:
        return clear


def snmp_walker(host, snmpVer, **kwargs):
    """在操作系统中执行snmpbulkwalk命令获取数据。

    参考：
    - SNMP体系架构：https://www.h3c.com/cn/d_202012/1364071_30005_0.htm
    
    参数：
    - host：目标IP；
    - snmpVer：SNMP版本；
    - **kwargs：snmpbulkwalk命令参数；
      - community：团体字，使用1或2c协议时填写；
      - authProtocol：认证协议；
      - level：认证级别；
      - context：上下文；
      - username：用户名；
      - authPasswd：密码；
      - privProtocol：加密协议；
      - privPasswd：加密密码；
    """
    def snmp_1_2c(oid, host=host, community=kwargs.get("community")):
        # snmpwalk为单行输出，snmpbulkwalk为批量输出，用于采集速度更快，能够节约将近3/4的时间
        cmd = "snmpbulkwalk -v 2c -c %s %s %s" % (community, host, oid)
        status, result = single_command2(cmd)

        # 对于非v3版本，没有明确标志获得团体字错误状态
        if not status:
            if "Timeout:" in result:
                logger.error(u"响应超时，OID：%s" % oid)
                return ""
            elif "No Such" in result:
                logger.warning(u"无此条目，OID：%s" % oid)
                return ""
            else:
                return ""
        
        return result


    def snmp_3(oid, host=host,
               authProtocol=kwargs.get("authProtocol"),
               level=kwargs.get("level"),
               context=kwargs.get("context"),
               username=kwargs.get("username"),
               authPasswd=kwargs.get("authPasswd"),
               privProtocol=kwargs.get("privProtocol"),
               privPasswd=kwargs.get("privPasswd")):
        _authProtocol = " -a %s" % authProtocol if authProtocol else ""
        _level = " -l %s" % level if level else ""
        _context = " -n %s" % context if context else ""
        _username = " -u %s" % username if username else ""
        _authPasswd = " -A %s" % authPasswd if authPasswd else ""
        _privProtocol = " -x %s" % privProtocol if privProtocol else ""
        _privPasswd = " -X %s" % privPasswd if privPasswd else ""
        
        paras = _authProtocol + _level + _context + _username + _authPasswd + _privProtocol + _privPasswd 
        cmd = "snmpbulkwalk -v 3 %s %s %s" % (paras, host, oid)
        status, result = single_command2(cmd)

        # 命令执行失败
        # 只对会导致全局失败的问题抛出异常，例如认知失败，如果只导致部分失败，则
        # 返回空字符串，例如timeout
        if not status:
            if "Authentication failure" in result:
                raise Exception("Authentication failure")
            elif "Timeout:" in result:
                logger.error(u"响应超时，OID：%s" % oid)
                return ""
            elif "No Such" in result:
                logger.warning(u"无此条目，OID：%s" % oid)
                return ""
            else:
                return ""
        
        return result

    if snmpVer in ["1", "2c"]:
        return snmp_1_2c
    elif snmpVer == "3":
        return snmp_3
    else:
        logger.error(u"SNMP版本错误")
        raise Exception("snmp version error")
